/* http.c - HTTP queries
   Copyright (C) 2001-2002, 2007, 2015, 2016 Free Software Foundation, Inc.

   This file is part of GNU JWhois.

   GNU JWhois is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   GNU JWhois is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GNU JWhois.  If not, see <http://www.gnu.org/licenses/>.  */

#include <config.h>
#include "system.h"

/* Specification.  */
#include "http.h"

#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "init.h"
#include "jconfig.h"
#include "lookup.h"
#include "utils.h"

/*
 * This function performs a HTTP query using an external utility
 * for the given host. The result is stored in the text parameter.
 *
 * Returns -1 on error, 0 on success.
 */
int http_query (whois_query_t wq, char **text)
{
    const char *method = get_whois_server_option(wq->host, "http-method");
    const char *action = get_whois_server_option(wq->host, "http-action");
    const char *element= get_whois_server_option(wq->host, "form-element");
    const char *extra  = get_whois_server_option(wq->host, "form-extra");
    const char *format = get_whois_server_option(wq->host, "query-format");
    char **command;
    char *url;
    char *browser;
    char *browser_arg;
    char *post_file = NULL;
    int isget = 1;
    int post_as_file = 0;
    int to_browser[2];
    int from_browser[2];
    struct jconfig *j;

    /* Check host configuration */
    if (!method || !action || !(element || format))
    {
        printf("[HTTP: %s: %s]\n", wq->host, _("HTTP configuration is incomplete:"));
        if (!method)
	  printf("[HTTP: %s %s]\n", _("Option is missing:"), "http-method");
        if (!action)
	  printf("[HTTP: %s %s]\n", _("Option is missing:"), "http-action");
        if (!element && !format)
	  printf("[HTTP: %s %s]\n", _("Option is missing:"), "form-element");
        return -1;
    }

    if (STREQ (method, "POST"))
      {
        isget = 0;
      }
    else if (!STREQ (method, "GET"))
      {
        printf("[HTTP: %s: %s]\n", wq->host,
	       _("Option http-method must be \"GET\" or \"POST\".\n"));
        return -1;
      }

    /* Check browser configuration */
    j = jconfig_getone("jwhois", "browser-pathname");
    if (!j)
    {
        printf("[HTTP: %s: %s]\n", _("Option is missing:"),
               "browser-pathname");
        return -1;
    }
    browser = j->value;

    j = jconfig_getone("jwhois", "browser-stdarg");
    if (!j)
    {
        printf("[HTTP: %s: %s]\n", _("Option is missing:"),
               "browser-stdarg");
        return -1;
    }
    browser_arg = j->value;

    j = jconfig_getone("jwhois", "post-as-file");
    if (j && STRCASEEQ (j->value, "true"))
      {
        post_as_file = 1;
      }
    
    /* Build command line */
    if (isget)
      {

        command = (char **) malloc(sizeof (char *) * 5);
        if (!command)
	  return -1;

        command[0] = browser;
        command[1] = command[0];
        command[2] = browser_arg;
	
        if (format)
	  {
            /* Query already formatted */
            command[3] = (char *) malloc(strlen("http://") + strlen(wq->host) +
                                         strlen(action) + 1 +
                                         strlen(wq->query) + 1);
            if (!command[3])
	      return -1;

	    sprintf(command[3], "http://%s%s?%s",
                    wq->host, action, wq->query);
        }
        else
        {
	  /* Format query using supplied data */
	  command[3] = (char *) malloc(strlen("http://") + strlen(wq->host) +
				       strlen(action) + 1 +
				       strlen(element) + 1 +
				       strlen(wq->query) +
				       (extra ? strlen(extra) + 1 : 0) + 1);
            if (!command[3])
	      return -1;

	    sprintf(command[3], "http://%s%s?%s=%s%s%s",
                    wq->host, action, element, wq->query,
                    extra ? "&" : "", extra ? extra : "");
        }
        command[4] = NULL;

        url = command[3];
      }
    else
      {
        int i = 0;

        command = (char **) malloc(sizeof (char *) * (6 + post_as_file));
        if (!command)
	  return -1;

        command[i++] = browser;
        command[i++] = command[0];
        command[i++] = browser_arg;

        j = jconfig_getone("jwhois", "browser-postarg");
        if (!j)
        {
	  printf("[HTTP: %s: %s]\n", _("Option is missing:"),
		 "browser-postarg");
	  return -1;
        }
        command[i++] = j->value;

        if (post_as_file)
	  {
            /* Create a temporary file to store POST data in */
            FILE *postdata;
            char tmpfilename[FILENAME_MAX];
            char *tmpdir;
            int fd;

            /* Use $TMPDIR if defined */
	    tmpdir = getenv("TMPDIR");
            if (tmpdir != NULL)
	      snprintf(tmpfilename, FILENAME_MAX, "%s/jwhoisXXXXXX", tmpdir);
	    else
	      strcpy(tmpfilename, "/tmp/jwhoisXXXXXX");
            
            /* Create the file */
            fd = mkstemp(tmpfilename);
            if (-1 == fd)
            {
                /* Couldn't create temporary file for POST data */
	      printf("[HTTP: %s: %s]\n", _("Unable to create temporary file"),
		     strerror(errno));
	      exit (EXIT_FAILURE);
            }
	    
            postdata = fdopen(fd, "w");

            if (format)
	      {
	      /* Query already formatted */
	      fprintf(postdata, "%s\n",
		      wq->query);
	      }
            else
	      {
                /* Format query using supplied data */
                fprintf(postdata, "%s=%s%s%s\n", element, wq->query,
                        extra ? "&" : "", extra ? extra : "");
	      }
            fclose(postdata);

            /* Remember file name so we can clean it up later. */
            command[i ++] = post_file = xstrdup(tmpfilename);
	  }

        command[i] = (char *) malloc(strlen("http://") + strlen(wq->host) +
                                     strlen(action) + 1);
        if (!command[i])
	  return -1;
        sprintf(command[i], "http://%s%s",
                wq->host, action);
        i++;

        command[i++] = NULL;

        url = command[i - 2];
      }

    if (arguments->verbose > 1)
    {
      int i;
      
      fputs("[Running ", stdout);
      for (i = 1; command[i]; i++)
        {
	  fputs(command[i], stdout);
	  fputc(' ', stdout);
        }
      puts("]");
    }

    /* Go on and do something */
    printf("[%s http://%s%s]\n", _("Querying"), wq->host, action);
    *text = NULL;

    /* Setup communication pipes */
    if (0 != pipe(from_browser) || 0 != pipe(to_browser))
    {
      free(command);
      return -1;
    }
    
    /* Run and retrieve data */
    if (0 == fork())
    {
      /* This is the child process */
      close(to_browser[1]);
      close(from_browser[0]);
      dup2(to_browser[0], 0); /* stdin */
      dup2(from_browser[1], 1); /* stdout */
      dup2(from_browser[1], 2); /* stderr */

	/* Drop special privileges */
      setreuid(getuid(), getuid());
      setregid(getgid(), getgid());
      
      /* Run the browser */
      execv(command[0], &command[1]);
      
      /* Drats! */
      if (errno)
        {
	  printf("[HTTP: %s: %s: %s]\n", _("Unable to run web browser"),
		 command[0], strerror(errno));
        }
      close(to_browser[0]);
      close(from_browser[1]);
      exit (EXIT_FAILURE);
    }
    else
      {
        /* This is the parent process */
        char data[MAXBUFSIZE];
        int datalen;
        int readbytes;
	
        close(to_browser[0]);
        close(from_browser[1]);
	
        if (!isget && !post_as_file)
	  {
            struct sigaction sa, old_sa;
	    
            /* Send POST data */
            if (format)
	      {
                /* Query already formatted */
                snprintf(data, MAXBUFSIZE, "%s\n---\n",
                         wq->query);
	      }
            else
	      {
                /* Format query using supplied data */
                snprintf(data, MAXBUFSIZE, "%s=%s%s%s\n---\n",
                         element, wq->query,
                         extra ? "&" : "", extra ? extra : "");
	      }
            sa.sa_handler = SIG_IGN;
            sigemptyset (&sa.sa_mask);
            sa.sa_flags = 0;
            sigaction (SIGPIPE, &sa, &old_sa);
            write(to_browser[1], data, strlen(data) + 1);
            sigaction (SIGPIPE, &old_sa, NULL);
	  }
	
        close(to_browser[1]);
	
        /* Get data from browser */
        *text = NULL;
        datalen = 0;
        data[MAXBUFSIZE - 1] = 0;
        while ((readbytes = read(from_browser[0], data, sizeof data - 1)) > 0)
	  {
            *text = (char *) realloc(*text, datalen + readbytes + 1);
            data[readbytes] = 0;
            strcpy(*text + datalen, data);
            datalen += readbytes;
	  }
	
        close(from_browser[0]);
      }
    
    /* Cleanup */
    free(url);
    free(command);
    wait(NULL);
    
    if (post_as_file)
      {
        /* Kill the temporary file */
        remove(post_file);
        free(post_file);
      }
    
    return 0;
}
